/***************************************************************************
      File Name:  MemberHelpers.cs
   File Created:  Friday, September 7th 2007
      Copyright:  (C) 2007 by Darko Matesic
          Email:  darko.matesic@yahoo.com
 ***************************************************************************/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Text;

namespace CallerID.Helpers
{
    /// <summary>
    /// Class with methods for handling class members.
    /// </summary>
    public class MemberHelpers
    {

        /// <summary>
        /// Returns TypeConverter for specified field or property.
        /// </summary>
        private static TypeConverter GetTypeConverter(MemberInfo mi)
        {
            if(mi != null) {
                if(mi.MemberType == MemberTypes.Field) {
                    FieldInfo fi = mi as FieldInfo;
                    if(fi != null) return TypeDescriptor.GetConverter(fi.FieldType);
                }
                if(mi.MemberType == MemberTypes.Property) {
                    PropertyInfo pi = mi as PropertyInfo;
                    if(pi != null) return TypeDescriptor.GetConverter(pi.PropertyType);
                }
            }
            return null;
        }

        /// <summary>
        /// Sets object field or property to specified string value.
        /// </summary>
        public static bool SetMemberValueAsString(object obj, MemberInfo mi, string value, ref Exception ex)
        {
            if(obj != null && mi != null && (mi.MemberType == MemberTypes.Property || mi.MemberType == MemberTypes.Field)) {
                // use a type converter to convert string value
                try {
                    TypeConverter tc = GetTypeConverter(mi);
                    if(tc != null) {
                        if(mi.MemberType == MemberTypes.Property) {
                            PropertyInfo pi = mi as PropertyInfo;
                            if(pi != null) {
                                if(tc.CanConvertFrom(typeof(string))) {
                                    object val = tc.ConvertFromInvariantString(value);
                                    pi.SetValue(obj, val, null);
                                    return true;
                                } else if(pi.PropertyType == typeof(Type)) {
                                    pi.SetValue(obj, Type.GetType(value), null);
                                    return true;
                                }
                            }
                        } else if(mi.MemberType == MemberTypes.Field) {
                            FieldInfo fi = mi as FieldInfo;
                            if(fi != null) {
                                if(tc.CanConvertFrom(typeof(string))) {
                                    object val = tc.ConvertFromInvariantString(value);
                                    fi.SetValue(obj, val);
                                    return true;
                                } else if(fi.FieldType == typeof(Type)) {
                                    fi.SetValue(obj, Type.GetType(value));
                                    return true;
                                }
                            }
                        }
                    }
                } catch(Exception e) {
                    ex = e;
                }
            }
            return false;
        }
        /// <summary>
        /// Sets object field or property to specified string value.
        /// </summary>
        public static bool SetMemberValueAsString(object obj, MemberInfo mi, string value)
        {
            Exception ex = null;
            return SetMemberValueAsString(obj, mi, value, ref ex);
        }


        /// <summary>
        /// Gets field or property value in string form.
        /// </summary>
        public static bool GetMemberValueAsString(object obj, MemberInfo mi, ref string value, ref Exception ex)
        {
            if(obj != null && mi != null && (mi.MemberType == MemberTypes.Property || mi.MemberType == MemberTypes.Field)) {
                try {
                    TypeConverter tc = GetTypeConverter(mi);
                    if(tc != null) {
                        if(mi.MemberType == MemberTypes.Property) {
                            PropertyInfo pi = mi as PropertyInfo;
                            if(pi != null) {
                                object objValue = pi.GetValue(obj, null);
                                if(tc.CanConvertFrom(typeof(string)) && tc.CanConvertTo(typeof(string))) {
                                    value = tc.ConvertToInvariantString(objValue);
                                    return true;
                                } else if(pi.PropertyType == typeof(Type)) {
                                    value = ((Type)objValue).AssemblyQualifiedName;
                                    return true;
                                }
                            }
                        } else if(mi.MemberType == MemberTypes.Field) {
                            FieldInfo fi = mi as FieldInfo;
                            if(fi != null) {
                                object objValue = fi.GetValue(obj);
                                if(tc.CanConvertFrom(typeof(string)) && tc.CanConvertTo(typeof(string))) {
                                    value = tc.ConvertToInvariantString(objValue);
                                    return true;
                                } else if(fi.FieldType == typeof(Type)) {
                                    value = ((Type)objValue).AssemblyQualifiedName;
                                    return true;
                                }
                            }
                        }
                    }
                } catch(Exception e) {
                    ex = e;
                }
            }
            return false;
        }
        /// <summary>
        /// Gets field or property value in string form.
        /// </summary>
        public static bool GetMemberValueAsString(object obj, MemberInfo mi, ref string value)
        {
            Exception ex = null;
            return GetMemberValueAsString(obj, mi, ref value, ref ex);
        }

        /// <summary>
        /// Returns fields for specifed type and all it's base types
        /// </summary>
        public static FieldInfo[] GetFields(Type type, BindingFlags bindingAttr)
        {
            if(type != null) {
                List<FieldInfo> list = new List<FieldInfo>();
                list.AddRange(type.GetFields(bindingAttr));
                if(type.BaseType != null) list.AddRange(GetFields(type.BaseType, bindingAttr));
                return list.ToArray();
            }
            return new FieldInfo[] { };
        }

        /// <summary>
        /// Returns fields for specifed type and all it's base types
        /// </summary>
        public static FieldInfo[] GetFields(Type type, string fieldName, BindingFlags bindingAttr)
        {
            if(type != null) {
                List<FieldInfo> list = new List<FieldInfo>();
                FieldInfo info = type.GetField(fieldName, bindingAttr);
                if(info != null) list.Add(info);
                if(type.BaseType != null) list.AddRange(GetFields(type.BaseType, fieldName, bindingAttr));
                return list.ToArray();
            }
            return new FieldInfo[] { };
        }

        /// <summary>
        /// Returns PropertyInfo from specified type with specified name.
        /// If property name is Ambiguous it returns first found property.
        /// </summary>
        public static PropertyInfo GetProperty(Type type, string name)
        {
            if(type != null && name != null && name.Length > 0) {
                try {
                    return type.GetProperty(name);
                } catch(AmbiguousMatchException) {
                    foreach(PropertyInfo prop in type.GetProperties()) {
                        if(prop.Name.StartsWith("Controls")) return prop;
                    }
                } catch { }
            }
            return null;
        }

        /// <summary>
        /// Returns PropertyDescriptor of specified property name from speicified object.
        /// </summary>
        public static PropertyDescriptor GetProperty(object obj, string name)
        {
            if(obj != null && name != null && name.Length > 0) {
                try {
                    if(name.Contains(".")) {
                        string[] names = name.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
                        if(names.Length > 0) {
                            for(int i = 0; i < names.Length; i++) {
                                PropertyDescriptor prop = TypeDescriptor.GetProperties(obj)[names[i]];
                                if(i == (names.Length - 1)) return prop;
                                if(prop == null) return null;
                                obj = prop.GetValue(obj);
                                if(obj == null) return null;
                            }
                        }
                    } else {
                        return TypeDescriptor.GetProperties(obj)[name];
                    }
                } catch { }
            }
            return null;
        }

        /// <summary>
        /// Returns value of specified property name from speicified object.
        /// </summary>
        public static object GetPropertyValue(object obj, string name)
        {
            if(obj != null && name != null && name.Length > 0) {
                try {
                    if(name.Contains(".")) {
                        string[] names = name.Split(new char[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
                        if(names.Length > 0) {
                            for(int i = 0; i < names.Length; i++) {
                                PropertyDescriptor prop = TypeDescriptor.GetProperties(obj)[names[i]];
                                if(prop == null) return null;
                                obj = prop.GetValue(obj);
                                if(obj == null) return null;
                                if(i == (names.Length - 1)) return obj;
                            }
                        }
                    } else {
                        return TypeDescriptor.GetProperties(obj)[name].GetValue(obj);
                    }
                } catch { }
            }
            return null;
        }

        /// <summary>
        /// Returns PropertyInfo from specified PropertyDescriptor.
        /// </summary>
        public static PropertyInfo GetPropertyInfo(PropertyDescriptor pd)
        {
            if(pd != null) {
                try {
                    return pd.ComponentType.GetProperty(pd.Name);
                } catch { }
            }
            return null;
        }

        /// <summary>
        /// Returns PropertyDescriptor for specified object and PropertyInfo.
        /// </summary>
        public static PropertyDescriptor GetPropertyDescriptor(object obj, PropertyInfo pi)
        {
            if(pi != null) {
                try {
                    return TypeDescriptor.GetProperties(obj)[pi.Name];
                } catch { }
            }
            return null;
        }

        /// <summary>
        /// Returns EventInfo from specified EventDescriptor.
        /// </summary>
        public static EventInfo GetEventInfo(EventDescriptor ed)
        {
            if(ed != null) {
                try {
                    return ed.ComponentType.GetEvent(ed.Name);
                } catch { }
            }
            return null;
        }

        /// <summary>
        /// Returns EventDescriptor for specified object and EventInfo.
        /// </summary>
        public static EventDescriptor GetEventDescriptor(object obj, EventInfo ei)
        {
            if(ei != null) {
                try {
                    return TypeDescriptor.GetEvents(obj)[ei.Name];
                } catch { }
            }
            return null;
        }

        /// <summary>
        /// Returns event subscribers for specified event name of specified target.
        /// </summary>
        public static Delegate[] GetEventSubscribers(object target, string eventName)
        {
            try {
                string WinFormsEventName = ("Event" + eventName);
                Type type = target.GetType();
                do {
                    foreach(FieldInfo fi in type.GetFields(BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance)) {
                        if(fi.Name == eventName) {
                            Delegate d = fi.GetValue(target) as Delegate;
                            if(d != null) return d.GetInvocationList();
                        }
                        if(fi.Name == WinFormsEventName) {
                            EventHandlerList ehl = (EventHandlerList)target.GetType().GetProperty("Events", (BindingFlags.FlattenHierarchy | (BindingFlags.NonPublic | BindingFlags.Instance))).GetValue(target, null);
                            Delegate d = ehl[fi.GetValue(target)];
                            if(d != null) return d.GetInvocationList();
                        }
                    }
                    type = type.BaseType;
                } while(type != null);
            } catch { }
            return new Delegate[0];
        }


        /// <summary>
        /// Saves specified object properties values to a string.
        /// </summary>
        public static string SaveProperties(object obj)
        {
            if(obj != null) {
                StringBuilder s = new StringBuilder();
                foreach(PropertyInfo pi in obj.GetType().GetProperties()) {
                    string value = "";
                    if(pi.CanWrite && GetMemberValueAsString(obj, pi, ref value)) {
                        if(s.Length > 0) s.Append('|');
                        s.Append(pi.Name + "=" + StringHelpers.EscapeSpecialCharacters(value));
                    }
                }
                return s.ToString();
            }
            return "";
        }

        /// <summary>
        /// Saves specified object properties values to a string.
        /// </summary>
        public static void RestoreProperties(object obj, string savedProperties)
        {
            if(obj != null && savedProperties != null && savedProperties.Length > 0) {
                string[] properties = savedProperties.Split(new char[] { '|' }, StringSplitOptions.RemoveEmptyEntries);
                if(properties != null && properties.Length > 0) {
                    Type type = obj.GetType();
                    foreach(string property in properties) {
                        int index = property.IndexOf('=');
                        if(index > 0) {
                            string name = property.Substring(0, index);
                            if(name.Length > 0) {
                                PropertyInfo pi = type.GetProperty(name);
                                if(pi != null) {
                                    string value = property.Substring(index + 1);
                                    SetMemberValueAsString(obj, pi, StringHelpers.RestoreSpecialCharacters(value));
                                }
                            }
                        }
                    }
                }
            }
        }

    }
}
